import 'dart:io';

import 'package:analyzer/dart/ast/ast.dart';
import 'package:dart_bean/setting.dart';
import 'package:dart_bean/util/ext.dart';
import 'package:dart_bean/util/field_feature.dart';
import 'package:dart_bean/field_info.dart';
import 'package:dart_bean/generator.dart';
import 'package:dart_bean/util/part_generator.dart';

class EnumFieldFeature extends FieldFeature {
  static final Set<_DartEnumDeclaration> _enums = {};

  late final _enumTypes = _enums.map((e) => e.name);

  @override
  String? getFromJsonMethod(FieldInfo field, String type) {
    final e = _findEnum(field, type);
    if (e == null) return null;
    _genEnum(field, e);
    return getEnumFromJsonName(e.name);
  }

  @override
  String? toJsonRightValue(FieldInfo field, String instance, String type) {
    final notNullType = getNotNullType(type);
    if (_enumTypes.contains(notNullType)) {
      final df = _findEnum(field, notNullType)!;
      _genEnum(field, df);
      return "${_enumMapName(df.self)}[$instance]";
    }
    return null;
  }

  static _DartEnumDeclaration? _findEnum(FieldInfo field, String type) {
    final innerEnum = _enums.firstOrNull((e) => e.file == field.parent.file && e.name == type);
    if (innerEnum != null) return innerEnum;
    return _enums.firstOrNull((e) => e.name == type);
  }

  static void visitEnumDeclaration(File file, EnumDeclaration node) {
    if (_allEnumFieldsHasValidAnnotation(node)) _enums.add(_DartEnumDeclaration(file, node));
  }

  static bool _allEnumFieldsHasValidAnnotation(EnumDeclaration declaration) {
    for (var e in declaration.constants) {
      final hasJsonValue = e.metadata.any((a) => a.name.name == 'JsonValue');
      if (!hasJsonValue) return false;
    }
    return true;
  }

  void _genEnum(FieldInfo field, _DartEnumDeclaration e) {
    Generator.getGenPartMaterial(field.parent.file).tasks.add(_GenCodeTaskForEnum(field, e));
  }
}

/// An annotation used to specify how a enum value is serialized.
class JsonValue {
  /// The value to use when serializing and deserializing.
  ///
  /// Can be a [String] or an [int].
  final dynamic value;

  const JsonValue(this.value);
}

String _enumMapName(EnumDeclaration declaration) => getEnumMapName(declaration.name.toString());

String _enumFromJsonName(EnumDeclaration declaration) => getEnumFromJsonName(declaration.name.toString());

class _DartEnumDeclaration {
  final File file;
  final EnumDeclaration self;

  String get name => self.name.toString();

  _DartEnumDeclaration(this.file, this.self);

  String getImportPath(Directory libDir) {
    return file.path.replaceFirst(libDir.path, '').replaceAll('\\', '/');
  }
}

class _GenCodeTaskForEnum extends GenCodeTask {
  final FieldInfo field;
  final _DartEnumDeclaration e;

  _GenCodeTaskForEnum(this.field, this.e);

  @override
  void gen(GenCodeMaterial output) {
    output.consts.add(_genEnumMap(e.self));
    output.functions.add(_genEnumFromJson(e.self));
  }

  String _genEnumMap(EnumDeclaration node) {
    final kvs = node.constants.map((e) => _enumFieldToMapPair(node.name.toString(), e)).join('\n');
    return """
      const ${_enumMapName(node)} = {
        $kvs
      };
""";
  }

  String _genEnumFromJson(EnumDeclaration node) {
    final enumName = node.name;
    return """
      ///set default value for unknownEnumValue when declare field,<br>
      ///eg: $enumName e = $enumName.unknown;
      $enumName? ${_enumFromJsonName(node)}(dynamic json)=>$decodeEnumName(${_enumMapName(node)}, json);
""";
  }

  String _enumFieldToMapPair(String enumName, EnumConstantDeclaration field) {
    final jv = field.metadata.firstWhere((a) => a.name.name == 'JsonValue');
    final v = jv.arguments!.arguments.first.toString();
    return '$enumName.${field.name}: $v,';
  }

  @override
  int get hashCode => '${e.file.path}:${e.name}'.hashCode;

  @override
  bool operator ==(Object other) {
    return other is _GenCodeTaskForEnum && other.hashCode == hashCode;
  }

  @override
  String toString() {
    return 'GenCodeTaskForEnum:${getRelativePath(e.file, Generator.libDir)}:${e.name}:$hashCode';
  }
}
